<?php
/**
 * Provides methods for easily building API requests and interpreting return data. Defaults to secure 
 * communications over HTTPS protocol with sandbox environment.
 * @author aziebell
 */
class eBayFindingApi{
	// production environment variables
	private $productionEndpoint = 'svcs.ebay.com/services/search/FindingService/v1';
	private $productionAppId = 'eBayAust-8e30-465c-a6d4-b9285983c727';
	
	// sandbox environment variables
	private $sandboxEndpoint = 'svcs.sandbox.ebay.com/services/search/FindingService/v1';
	private $sandboxAppId = 'eBay751c8-8375-4f0a-bd25-d29fabb239e';
	
	// shared environment varoables
	private $globalId = 'EBAY-AU';
	private $secure = false;
	private $production = false;
	private $outputFormat = 'XML';
	private $outputSelector = '';
	
	// filter variables
	private $aspectFilter = array();
	private $itemFilter = array();
	private $sortOrder = null;
	
	// response data variables
	private $response = false;
	
	public function call($method, $params = array()) {		
		// construct get params for the method call
		$get = array(
			'OPERATION-NAME' => $method,
			'SERVICE-VERSION' => '1.8.0',
			'SECURITY-APPNAME' => $this->{($this->production ? 'production' : 'sandbox') . 'AppId'},
			'GLOBAL-ID' => $this->globalId,
			'RESPONSE-DATA-FORMAT' => $this->outputFormat,
			'REST_PAYLOAD' => null,
			'outputSelector' => $this->outputSelector
		);
		
		// add the $params to the $get array
		$get = array_merge($params, $get);
		
		// work out production / sandbox URL and protocol to use
		$url = $this->secure ? 'https://' : 'http://';
		$url .= $this->{($this->production ? 'production' : 'sandbox') . 'Endpoint'};
		$url .= '?';
			
		// append GET vars to url
		foreach($get as $var => $val) {
			$url .= $var.'='.urlencode($val).'&';
		}
		
		// attach aspect filter params		
		$url .= $this->getAspectParams();
		
		// attach item filter params		
		$url .= $this->getItemParams();
		
		// apply applicable sort order
		if(!empty($this->sortOrder)) {
			$url .= 'sortOrder='.urlencode($this->sortOrder).'&';
		}
		
		// remove trailing & OR ? (if there is no GET/filter vars)
		$url = substr($url, 0, strlen($url)-1);
		
		// initialise curl
		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		
		$this->response = curl_exec($ch);
		curl_close($ch);
		
		// return instance of api for chaining
		return $this;
	}
	
	/**
	 * Returns the response data as a DOMDocument or XML string.
	 * @param bool $domDoc - return as DOM document or as XML string
	 */
	public function getResponse($domDoc = true) {
		if($domDoc)
			return DOMDocument::loadXML($this->response);
		
		return $this->response;
	}
	
	/**
	 * Enter description here ...
	 * @param bool $aspects - override with false to remove AspectHistogram from result
	 * @return obj $this
	 * @throws Exception
	 */
	public function returnAspects($aspects = true) {
		if(is_bool($aspects)) {
			$this->outputSelector = $aspects ? 'AspectHistogram' : '';
			
			// return instance of api for chaining
			return $this;
		}
		
		// @todo should use framework error handler
		throw new Exception(get_class($this).": returnAspects arguement must be of type boolean.");
	}
	
	/**
	 * Returns the item filter param string for API calls.
	 */
	public function getItemParams() {
		$params = '';
		$item = 0;
		
		foreach($this->itemFilter as $key => $val) {
			$params .= 'itemFilter('.$item.').name='.urlencode($key).'&';
			
			if(is_array($val)) {
				$x=0;
				foreach($val as $v) {
					$params .= 'itemFilter('.$item.').value('.$x.')='.urlencode($v).'&';
					$x++;
				}
			} else {
				$params .= 'itemFilter('.$item.').value='.urlencode($val).'&';
			}
			
			
			$item++;
		}
		
		return $params;
	}
	
	/**
	 * Returns the aspect filter param string for API calls.
	 */
	public function getAspectParams() {
		$params = '';
		$aspect = 0;
		
		foreach($this->aspectFilter as $key => $val) {
			$params .= 'aspectFilter('.$aspect.').aspectName='.urlencode($key).'&';			
			$params .= 'aspectFilter('.$aspect.').aspectValueName='.urlencode($val).'&';
			$aspect++;
		}
		
		return $params;
	}
	
	/**
	 * Sets the sort order of the API results.
	 * @param str $order
	 */
	public function setSortOrder($order) {
		$this->sortOrder = $order;
		return $this;
	}
	
	/**
	 * Sets item array key / value(s) pairs to the item filter.
	 * @param array $items
	 * @return obj $this
	 * @throws Exception
	 */
	public function setItemFilters($items) {
		if(is_array($items)) {
			$this->itemFilter = $items;
			
			// return instance of api for chaining
			return $this;
		}
		
		// @todo should use framework error handler
		throw new Exception(get_class($this).": setItemFilters arguement must be of type array.");
	}
	
	/**
	 * Sets aspect array key / value(s) pairs to the aspect filter.
	 * @param array $aspects
	 * @return obj $this
	 * @throws Exception
	 */
	public function setAspectFilters($aspects) {
		if(is_array($aspects)) {
			$this->aspectFilter = $aspects;
			
			// return instance of api for chaining
			return $this;
		}
		
		// @todo should use framework error handler
		throw new Exception(get_class($this).": setAspectFilters arguement must be of type array.");
	}
	
	/**
	 * Secures communications by using the HTTPS protocol.
	 * @param bool $secure - override with false to remove HTTPS protocol use
	 * @return obj $this
	 */
	public function setSecure($secure = true) {
		if(is_bool($secure)) {
			// @todo doesn't seem to have curl HTTPS support
			//$this->secure = $secure;
			
			// return instance of api for chaining
			return $this;
		}
		
		// @todo should use framework error handler
		throw new Exception(get_class($this).": setSecure arguement must be of type boolean.");
	}
	
	/**
	 * Sets production and sandbox mode for the API call.
	 * @param bool $production - override with false to set to sandbox mode
	 * @return obj $this
	 */
	public function setProduction($production = true) {
		if(is_bool($production)) {
			$this->production = $production;
			
			// return instance of api for chaining
			return $this;
		}
		
		// @todo should use framework error handler
		throw new Exception(get_class($this).": setProduction arguement must be of type boolean.");
	}
	
	/**
	 * Sets the output selector for the API call
	 * @param string $selector
	 */
	public function setOutputSelector($selector) {
		$this->outputSelector = $selector;
		
		// return instance of api for chaining
		return $this;
	}
}

// for testing
function xmlIndent($str){
    $ret = "";
    $indent = 0;
    $indentInc = 3;
    $noIndent = false;
    while(($l = strpos($str,"<",$i))!==false){
        if($l!=$r && $indent>0){ $ret .= "\n" . str_repeat(" ",$indent) . substr($str,$r,($l-$r)); }
        $i = $l+1;
        $r = strpos($str,">",$i)+1;
        $t = substr($str,$l,($r-$l));
        if(strpos($t,"/")==1){
            $indent -= $indentInc;
            $noIndent = true;
        }
        else if(($r-$l-strpos($t,"/"))==2 || substr($t,0,2)=="<?"){ $noIndent = true; }
        if($indent<0){ $indent = 0; }
        if($ret){ $ret .= "\n"; }
        $ret .= str_repeat(" ",$indent);
        $ret .= $t;
        if(!$noIndent){ $indent += $indentInc; }
        $noIndent = false;
    }
    $ret .= "\n";
    return($ret);
} 